﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace SmartPlant.WinCore
{
    #region 生成自定义动态类
    /// <summary>
    /// 生成自定义动态类
    /// </summary>
    public static class DynamicClassHelper
    {
        private static readonly object _synObj = new();
        /// <summary>
        /// 记录已经用<see cref="CreateDynamicType(string, List{DynamicPropertyInfo})"/>生成动态类的集合，避免重复运行
        /// </summary>
        private static readonly Dictionary<string, Type> _typeObjs = new();

        /// <summary>
        /// 创建属性
        /// </summary>
        /// <param name="propertyType">属性基础类型(byte/int/string/float/double/bool等)和List基础类型</param>
        /// <param name="propertyName">属性名称</param>
        /// <param name="propertyDefaultValue">属性初始值</param>
        /// <returns></returns>
        private static string PropertyString(string propertyType, string propertyName, string propertyDefaultValue)
        {

            var sbproperty = new StringBuilder();
            sbproperty.AppendLine($" private {propertyType} _{propertyName};");
            sbproperty.AppendLine(" public " + propertyType + " " + propertyName + " { get{ return _" + propertyName + "; } set{ SetProperty(ref _" + propertyName + ", value); } }");
            return sbproperty.ToString();
        }

        /// <summary>
        /// 根据类型描述信息创建动态类
        /// </summary>
        /// <param name="className">动态类名字</param>
        /// <param name="propertyList">属性列表</param>
        /// <returns></returns>
        public static Type? CreateDynamicType(string className, List<DynamicPropertyInfo> propertyList)
        {
            if (_typeObjs.ContainsKey(className))
                return _typeObjs[className];//避免重复

            lock (_synObj)
            {
                var assembly = CreateAssembly(className, propertyList);
                if (assembly == null) return null;

                var type = assembly.GetType(className);
                _typeObjs.Add(className, type);
                return type;
            }
        }

        /// <summary>
        /// 创建动态程序集
        /// </summary>
        /// <param name="className">动态类名字</param>
        /// <param name="propertyList">属性列表</param>
        /// <returns></returns>
        public static Assembly? CreateAssembly(string className, List<DynamicPropertyInfo> propertyList)
        {
            //程序用到的所有引用dll
            var references = AppDomain.CurrentDomain
                            .GetAssemblies()
                            .Where(a => !a.IsDynamic)
                            .Select(a => a.Location)
                            .Where(s => !string.IsNullOrEmpty(s))
                            .Where(s => !s.Contains("xunit"))
                            .Select(s => MetadataReference.CreateFromFile(s))
                            .ToList();

            //创建编译器实例。 （一个新的dll，然后放到项目里的dll集合里去，相当于多了一个引用）
            var dllFileName = Guid.NewGuid().ToString() + ".dll";
            var compilationOptions = new CSharpCompilationOptions( //设置编译参数。
                    OutputKind.DynamicallyLinkedLibrary,
                    usings: null,
                    optimizationLevel: OptimizationLevel.Debug,
                    checkOverflow: false,
                    allowUnsafe: true,
                    platform: Platform.AnyCpu,
                    warningLevel: 4,
                    xmlReferenceResolver: null
                );
            var cSharpCompilation = CSharpCompilation.Create(dllFileName)
                .WithOptions(compilationOptions).AddReferences(references);

            //创建动态代码。（此时还是一个空壳子，需要再用代码去解析一份xxxx.cs）   
            var classsource = MakeCode(className, propertyList);

            try
            {
                using var memoryStream = new MemoryStream();

                var codeTree = CSharpSyntaxTree.ParseText(classsource);
                //编译代码。   
                var emitResult = cSharpCompilation.AddSyntaxTrees(codeTree).Emit(memoryStream);
                if (emitResult.Success)
                {
                    memoryStream.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(memoryStream.ToArray());
                    return assembly;
                }
                return null;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        /// <summary>
        /// 相当于用代码创建了一个xxx.cs内容
        /// </summary>
        /// <param name="className"></param>
        /// <param name="propertyList"></param>
        /// <returns></returns>
        private static string MakeCode(string className, List<DynamicPropertyInfo> propertyList)
        {
            //创建动态代码。   
            var classsource = new StringBuilder();
            classsource.Append("using System;\n");
            classsource.Append("using System.Collections.Generic;\n");
            classsource.Append("using SmartPlant.Model;\n");
            classsource.Append("public class " + className + " : DynamicEntity\n");
            classsource.Append("{\n");

            //创建属性。   
            for (int i = 0; i < propertyList.Count; i++)
            {
                var propertyStr = PropertyString(propertyList[i].PropertyType, propertyList[i].PropertyName, propertyList[i].PropertyDefaultValue);
                classsource.Append(propertyStr);
            }
            classsource.Append("}");
            System.Diagnostics.Debug.WriteLine(classsource.ToString());
            return classsource.ToString();
        }

        /// <summary>
        /// 给属性赋值
        /// </summary>
        /// <param name="objclass">先进行dynamic objclass = assembly.CreateInstance(className)，得到的objclass</param>
        /// <param name="propertyname">属性名称</param>
        /// <param name="value">属性值</param>
        public static void ReflectionSetValue(object objclass, string propertyname, object value)
        {
            PropertyInfo[] infos = objclass.GetType().GetProperties();

            try
            {
                foreach (PropertyInfo info in infos)
                {
                    if (info.Name == propertyname && info.CanWrite)
                    {
                        info.SetValue(objclass, value, null);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

        }
        /// <summary>
        /// 得到属性值
        /// </summary>
        /// <param name="objclass">先进行dynamic objclass = assembly.CreateInstance(className)，得到的objclass</param>
        /// <param name="propertyname">属性名称</param>
        /// <returns>属性值，是object类型，使用时记得转换</returns>
        public static object? ReflectionGetValue(object objclass, string propertyname)
        {
            object? result = null;
            PropertyInfo[] infos = objclass.GetType().GetProperties();
            try
            {
                foreach (PropertyInfo info in infos)
                {
                    if (info.Name == propertyname && info.CanRead)
                    {
                        result = info.GetValue(objclass, null);
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return result;
        }
    }
    #endregion    
}
